package com.uxsino.simo.collector.connections;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.cim.CIMInstance;
import javax.cim.CIMObjectPath;
import javax.cim.CIMProperty;
import javax.security.auth.Subject;
import javax.wbem.CloseableIterator;
import javax.wbem.WBEMException;
import javax.wbem.client.PasswordCredential;
import javax.wbem.client.UserPrincipal;
import javax.wbem.client.WBEMClient;
import javax.wbem.client.WBEMClientConstants;
import javax.wbem.client.WBEMClientFactory;

import lombok.Data;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.uxsino.simo.connections.AbstractConnection;
import com.uxsino.simo.connections.exception.SimoConnectionException;
import com.uxsino.simo.connections.exception.SimoQueryException;
import com.uxsino.simo.connections.target.SMISTarget;

public class SMISConnection extends AbstractConnection<SMISTarget> {

    @Data
    private class SMISCmd {

        private String className;

        private String[] propertys;

        SMISCmd(String cmd, String args) {
            className = cmd;
            propertys = args.split(",");
            for (int i = 0; i < propertys.length; i++) {
                propertys[i] = propertys[i].trim();
            }
        }

    }

    private static Logger logger = LoggerFactory.getLogger(SMISConnection.class);

    private final static String URL_FLAG = "://";

    private final static String COLON = ":";

    private final static int CONN_TIMEOUT = 7000;

    private WBEMClient wbClient;

    private String netAddress;

    @Override
    public Object buildCmd(String cmdPattern, Map<String, String> args) {
        // return new SMISCmd((String) cmdPattern.getCmdTemplate(), args.get("args"));
        return new SMISCmd(cmdPattern, args.get("args"));
    }

    @Override
    public int close() {
        super.close();
        if (wbClient != null) {
            try {
            wbClient.close();
            }catch (Exception e){
                logger.error("close smis connection error: ", e);
            }
            netAddress = null;
        }
        connected = false;
        super.close();
        return -1;
    }

    @Override
    public int connect(SMISTarget target) {
        super.connect(target);
        SMISTarget smi_target = target;
        netAddress = createAddr(smi_target);

        this.target = smi_target;
        connected = false;
        CIMObjectPath cns = new CIMObjectPath(netAddress);
        UserPrincipal up = new UserPrincipal(smi_target.getUsername());
        PasswordCredential pc = new PasswordCredential(smi_target.getPassword());
        /* 把账号及密码添加到主题中 */
        Subject s = new Subject();
        s.getPrincipals().add(up);
        s.getPrivateCredentials().add(pc);
        try {
            wbClient = WBEMClientFactory.getClient(WBEMClientConstants.PROTOCOL_CIMXML);
            wbClient.initialize(cns, s, null);
            connected = true;
            state = 1;
        } catch (WBEMException e) {
            logger.error("smi-s 连接创建失败.原因:{}", "", e);
            close();
            state = 0;
        }

        return state;
    }

    private String createAddr(SMISTarget target) {
        StringBuilder builder = new StringBuilder();
        builder.append(target.getScheme()).append(URL_FLAG).append(target.host).append(COLON).append(target.port)
            .append(target.getNamespace());
        return builder.toString();
    }

    @Override
    public Object execCmd(Object cmdPattern) throws SimoQueryException, SimoConnectionException {
        if (!isConnected()) {
            logger.error("smi-s service can not exec command for it is not connected!");
            throw new SimoConnectionException("no connection valid.");
        }

        SMISCmd smisCmd = (SMISCmd) cmdPattern;
        CIMObjectPath cop = new CIMObjectPath(target.getNamespace() + COLON + smisCmd.getClassName());

        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<CloseableIterator<CIMInstance>> future = executor.submit(new Callable<CloseableIterator<CIMInstance>>() {
            @Override
            public CloseableIterator<CIMInstance> call() throws SimoQueryException {
                try {
                    return wbClient.enumerateInstances(cop, true, false, false, smisCmd.getPropertys());
                } catch (WBEMException e) {
                    logger.error("wbem client enumerate instance error ", e);
                    throw new SimoQueryException(e);
                }
            }
        });
        CloseableIterator<CIMInstance> cmiInstance;
        try {
            cmiInstance = future.get(CONN_TIMEOUT, TimeUnit.MILLISECONDS);
            return wrapJSONArray(cmiInstance);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            logger.error("future get cmi instance error", e);
            throw new SimoQueryException(e);
        }
        /*
        CloseableIterator<CIMInstance> cmiInstance = wbClient.enumerateInstances(cop, true, false, false,
            smisCmd.getPropertys());
        */

    }

    private JSONArray wrapJSONArray(CloseableIterator<CIMInstance> ei) {
        JSONArray array = new JSONArray();
        while (ei.hasNext()) {
            CIMInstance instance = ei.next();
            CIMProperty<?>[] properties = instance.getProperties();
            JSONObject object = new JSONObject();
            for (CIMProperty<?> property : properties) {
                Object value = property.getValue();
                if (value != null) {
                    if (value.getClass().isArray()) {
                        Object[] temp = (Object[]) value;
                        List<Object> list = Arrays.asList(temp);
                        value = list.toString();
                    } else {
                        value = value.toString();
                    }
                    object.put(property.getName(), value);
                }
            }
            array.add(object);
        }
        return array;
    }

    @Override
    public boolean testWithConnected(String cmdString, String resStart) {
        String[] cmdArr = cmdString.split(":");
        SMISCmd smisCmd = new SMISCmd(cmdArr[0], cmdArr[1]);
        Object cmdRes = null;
        try {
            cmdRes = execCmd(smisCmd);
        } catch (SimoQueryException | SimoConnectionException e) {
            logger.error("execute command failed : {}", e);
        }
        if (cmdRes == null) {
            connected = false;
        }
        return connected;
    }

}
